﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;

namespace hydratingDLL
{
    /// <summary>
    /// after an idea of  PauloMorgado
    /// http://msmvps.com/blogs/paulomorgado/archive/2010/08/16/hydrating-objects-with-expression-trees-part-ii.aspx
    /// Uses:  from a string PropertyName and a value, re-construct object
    /// see console project   
    /// </summary>
    /// <typeparam name="T"></typeparam>   
    public class HydrateGeneric<T>
        where T:class,new()
    {
        List<MemberBinding> bindings;
        List<object> values;
        Type type;
        PropertyInfo[] properties;
        ParameterExpression valuesArrayExpression;
        /// <summary>
        /// constructor - initialize various properties
        /// </summary>
        public HydrateGeneric()
        {
            bindings = new List<MemberBinding>();
            values = new List<object>();
            type = typeof(T);
            properties = type.GetProperties().Where(item => item.CanWrite).ToArray();
            valuesArrayExpression = Expression.Parameter(typeof(object[]), "v");            
        }
        private MemberBinding PropFrom(string NameProp, Type t, out Type LastType)
        {
            Stack<PropertyInfo> pi = new Stack<PropertyInfo>();
            int dot = NameProp.IndexOf(".");            
            Type current =t;
            while (dot > -1)
            {
                string realNameProp = NameProp.Substring(0, dot) ;    
                var prop = current.GetProperty(realNameProp);
                pi.Push(prop);
                current = prop.PropertyType;
                NameProp = NameProp.Substring(dot + 1);
                dot = NameProp.IndexOf(".");

            }
            var property = current.GetProperty(NameProp);
            LastType =property.PropertyType;
            int p = bindings.Count;
            var exp = Expression.Convert(
                    Expression.ArrayAccess(
                        valuesArrayExpression,
                        Expression.Constant(p, typeof(int))
                    ),
                    LastType
                );

            MemberBinding b = Expression.Bind(property, exp);
            while (pi.Count > 0)
            {                
                var prop = pi.Pop();
                b = Expression.MemberBind(prop,b);                

            }
            return b;

        }
        /// <summary>
        /// add a new property with his value
        /// e.g. "Employee.FirstName","Andrei"
        /// </summary>
        /// <param name="NameProp">can be Employee.FirstName</param>
        /// <param name="value">can be Andrei</param>
        public void AddNewProperty(string NameProp, string value)
        {
            Type LastType ;
            var b=PropFrom(NameProp,type,out LastType);
            bindings.Add(b);
            var newval = Convert.ChangeType(value, LastType);
            values.Add(newval);
            

           
        }
        private Expression<Func<object[],object>> ExpNewObject()
        {

            var memberInitExpression = Expression.MemberInit(Expression.New(type),bindings);
            var x = memberInitExpression.Reduce();
            
          return Expression.Lambda<Func<object[], object>>(memberInitExpression, valuesArrayExpression);

        }
        /// <summary>
        /// constructs the new object from properties
        /// </summary>
        /// <returns></returns>
        public T NewObject()
        {
            var compiledObjectHidrationExpression = ExpNewObject().Compile();
            return (T)compiledObjectHidrationExpression(values.ToArray()) ;
        }

    }
}
